#include <iostream>
using namespace std;
#include <vector>
#include<unistd.h>
#include<ctime>
#include<sys/wait.h>
#include<string>
#include<cstdlib>
#include"task.hpp"

enum // 枚举常量，代表各个错误返回码
{
    UsageError = 1,
    ArgError,
    PipeError
};

// 正确使用方法说明
void Usage(const string &proc)
{
    cout << "Usage:" << proc << "subprocess-num" << endl;
}

//先描述再组织，管理管道
class Channel
{
public:
    Channel(int wfd, pid_t sub_process_id, string name)
        : _wfd(wfd), _sub_process_id(sub_process_id), _name(name)
    {
    }
    ~Channel()
    {
    }
    //查看一个管道
    void PrintDebug()
    {
        cout<<"_wfd:"<<_wfd<<"_sub_process_id:"<<_sub_process_id<<"_name:"<<_name<<endl;
    }
    //提供一些get接口，获取属性
    string name()   {return _name;}
    pid_t pid()     {return _sub_process_id;}
    int wfd()   {return _wfd;}
    void Close()    {close(_wfd);}
private:
    int _wfd;              // 该管道文件被主进程master写端fd
    pid_t _sub_process_id; // 管道对应的子进程pid
    string _name;
};

// 定义内存池对象
class ProcessPool
{
public:
    ProcessPool(int sub_process_num)
        : _sub_process_num(sub_process_num)
    {
    }
    void KillAll()
    {
        for(auto& channel:_channels)
        {
            channel.Close();    //关闭该管道文件对应的父进程写端
            pid_t pid = channel.pid();
            pid_t rid = waitpid(pid, nullptr, 0);
            if(rid==pid)
            {
                cout<<"sub process:"<<pid<<"wait sucess"<<endl;
            }
        }
    }
   
    int CreateProcess(work_t work)     //回调函数
    {
        vector<int> fds;    //避免引用计数，子进程退出不了，所以修正一下
        for(int number = 0;number<_sub_process_num;number++)
        {
            //创建一个子进程之前，先创建对应的一个管道
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            if(n<0)
                return PipeError;
            pid_t id = fork();
            if(id == 0)
            {   
                //进行修正
                if(!fds.empty())
                {
                    cout<<"close w fd"<<" ";
                    for(auto fd:fds)
                    {
                        close(fd);
                        cout<<fd<<" ";
                    }
                    cout<<endl;
                }
                //child
                close(pipefd[1]);   //关闭写端
                //执行任务
                dup2(pipefd[0], 0); //输入重定向，0默认从键盘读入，现在默认从管道文件里面读入
                work(pipefd[0]); //  其实回调worker函数， 子进程阻塞等待读对应管道内容，等待父进程发送任务码
                exit(0);
            }
            //father
            string name = "channel-" + to_string(number);
            close(pipefd[0]);
            _channels.push_back(Channel(pipefd[1], id, name));
            //把父进程的wfd保存起来
            fds.push_back(pipefd[1]);
        }
        return 0;
    }
    int NextChannel()
    {
        static int next = 0;
        int c = next;
        next++;
        next%=_channels.size();
        return c;
    }
    void SendTaskCode(int index, uint32_t code)
    {
        cout << "send code: " << code << " to " << _channels[index].name() << " sub prorcess id: " << _channels[index].pid() << endl;
        write(_channels[index].wfd(), &code, sizeof(code));
    }
    void Debug()
    {
        for(auto& channel:_channels)
        {
            channel.PrintDebug();   //遍历所有的管道，所有的管道又是自定义类型，所以调用其打印函数可以查看该管道文件
        }
    }
private:
    int _sub_process_num;
    vector<Channel> _channels;
};

 void CtrlProcessPool(ProcessPool* processpool_ptr, int cnt)
 {
        while(cnt)      //负载均衡-》轮询式分配任务给对应进程
        {
            //1. 选择一个进程和通道
            int channel = processpool_ptr->NextChannel();   //负载均衡拿到一个对应的一个管道文件对应vector里面的下标

            //2. 选择一个任务
            uint32_t code = NextTask();     //随机选择一个任务码

            //3. 发送任务
            processpool_ptr->SendTaskCode(channel, code);
            sleep(1);
            cnt--;
        }
 }

// ./processpool  5
int main(int argc, char *argv[])
{
    if (argc != 2) // 如果命令行参数个数，不等于2，说明用法格式选项不对，提示正确用法
    {
        Usage(argv[0]);
        return UsageError;
    }
    // 1. 创建通信信道和子进程
    int sub_process_num = stoi(argv[1]);
    if(sub_process_num<=0)
        return ArgError;    //子进程数量一个或者多个
    srand((uint64_t)time(nullptr)); //设置随机数种子
    ProcessPool* processpool_ptr = new ProcessPool(sub_process_num);
    processpool_ptr->CreateProcess(worker);
    processpool_ptr->Debug();

    // 2.控制子进程
    CtrlProcessPool(processpool_ptr, 10);
    cout<<"task run done"<<endl;
    //3. 回收子进程
    processpool_ptr->KillAll(); //通过关闭所有管道文件的写端，对应子进程不再阻塞等待退出之后，回收所有子进程
    delete processpool_ptr;
    return 0;
}